package com.mine.jzh3.oss;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.CharsetUtil;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.*;
import com.mine.jzh3.oss.config.AliOSSClientManager;
import com.mine.jzh3.oss.config.AliOSSConfigService;
import com.mine.jzh3.oss.entity.AliOSSConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Future;
import java.util.regex.Pattern;

/**
 * 阿里云OSS操作实现
 */
@Slf4j
@Component
public class AliOSSOperation {
    private final AliOSSClientManager clientManager;
    private final AliOSSConfigService configService;
    private final ThreadPoolTaskExecutor ossCopyExecutor;

    public AliOSSOperation(AliOSSClientManager clientManager, AliOSSConfigService configService,
                           @Qualifier("ossCopyExecutor") ThreadPoolTaskExecutor ossCopyExecutor) {
        this.clientManager = clientManager;
        this.configService = configService;
        this.ossCopyExecutor = ossCopyExecutor;
    }

    /**
     * 上传文件
     */
    public String uploadFile(InputStream inputStream, String objectKey) {
        return this.uploadFile(null, inputStream, objectKey);
    }

    /**
     * 上传文件
     */
    public String uploadFile(String bucketName, InputStream inputStream, String objectKey) {
        boolean validObjectKey = this.isValidObjectKey(objectKey);
        if (!validObjectKey) {
            log.error("文件上传失败, 路径不合法: {}", objectKey);
            throw new IllegalArgumentException("文件上传失败, 路径不合法");
        }
        OSS client = getClient();
        String targetBucket = this.getBucketName(bucketName);

        try {
            client.putObject(targetBucket, objectKey, inputStream);
            log.info("文件上传成功: bucket={}, objectKey={}", targetBucket, objectKey);
            return getFileUrl(targetBucket, objectKey);
        } catch (Exception e) {
            log.error("文件上传失败: bucket={}, objectKey={}", targetBucket, objectKey, e);
            throw new RuntimeException("文件上传失败", e);
        }
    }

    /**
     * 删除文件
     */
    public void deleteFile(String objectKey) {
        this.deleteFile(null, objectKey);
    }

    /**
     * 删除文件
     */
    public void deleteFile(String bucketName, String objectKey) {
        boolean validObjectKey = this.isValidObjectKey(objectKey);
        if (!validObjectKey) {
            log.error("文件删除失败, 路径不合法: {}", objectKey);
            throw new IllegalArgumentException("文件删除失败, 路径不合法");
        }
        OSS client = getClient();
        String targetBucket = this.getBucketName(bucketName);

        try {
            client.deleteObject(targetBucket, objectKey);
            log.info("文件删除成功: bucket={}, objectKey={}", targetBucket, objectKey);
        } catch (Exception e) {
            log.error("删除文件失败: bucket={}, objectKey={}", bucketName, objectKey, e);
            throw new RuntimeException("删除文件失败", e);
        }
    }

    /**
     * 批量删除文件
     */
    public void deleteFiles(List<String> objectKeys) {
        deleteFiles(null, objectKeys);
    }

    /**
     * 批量删除文件（包含非法路径, 抛错）
     */
    public void deleteFiles(String bucketName, List<String> objectKeys) {
        this.validateObjectKeysOrThrow(objectKeys);

        this.deleteKeysFromBucket(bucketName, objectKeys);
    }

    /**
     * 批量安全删除文件（仅删除符合条件的文件，非法路径跳过）
     */
    public void safeDeleteFiles(String bucketName, List<String> objectKeys) {
        if (objectKeys == null || objectKeys.isEmpty()) {
            log.warn("未提供要删除的文件列表，操作跳过");
            return;
        }

        List<String> validKeys = filterValidObjectKeys(objectKeys);
        if (validKeys.isEmpty()) {
            log.warn("没有合法的ObjectKey需要删除，操作终止");
            return;
        }

        deleteKeysFromBucket(bucketName, validKeys);
    }

    /**
     * 获取文件URL
     */
    public String getFileUrl(String objectKey) {
        return this.getFileUrl(null, objectKey);
    }

    /**
     * 获取文件URL
     */
    public String getFileUrl(String bucketName, String objectKey) {
        OSS client = getClient();
        String targetBucket = this.getBucketName(bucketName);
        try {
            // 生成签名URL，1小时有效
            Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000);
            URL url = client.generatePresignedUrl(targetBucket, objectKey, expiration);
            return url.toString();
        } catch (Exception e) {
            log.error("获取文件URL失败: bucket={}, objectKey={}", bucketName, objectKey, e);
            throw new RuntimeException("获取文件URL失败", e);
        }
    }

    /**
     * 列出文件
     */
    public List<String> listFiles(String prefix) {
        return this.listFiles(null, prefix);
    }

    /**
     * 列出文件
     */
    public List<String> listFiles(String bucketName, String prefix) {
        OSS client = getClient();
        try {
            String targetBucket = this.getBucketName(bucketName);
            List<String> files = new ArrayList<>();

            // 创建请求
            ListObjectsRequest request = new ListObjectsRequest(targetBucket);
            request.setPrefix(prefix);

            // 列出对象
            ObjectListing result = client.listObjects(request);
            result.getObjectSummaries()
                    .forEach(summary -> files.add(summary.getKey()));

            return files;
        } catch (Exception e) {
            log.error("列出文件失败: bucket={}, prefix={}", bucketName, prefix, e);
            throw new RuntimeException("列出文件失败", e);
        }
    }

    /**
     * 安全下载文件（文件不存在则抛出异常）
     */
    public byte[] safeDownloadFile(String objectKey) {
        return this.safeDownloadFile(null, objectKey);
    }

    /**
     * 安全下载文件（文件不存在则抛出异常）
     */
    public byte[] safeDownloadFile(String bucketName, String objectKey) {
        OSS client = this.getClient();
        boolean objectExists = this.objectExists(client, bucketName, objectKey);
        if (!objectExists) {
            log.error("文件已删除或过期，下载失败: bucket={}, objectKey={}", bucketName, objectKey);
            throw new IllegalArgumentException("文件已删除或过期，下载失败");
        }

        return this.downloadFile(client, bucketName, objectKey);
    }

    /**
     * 下载文件，直接下载文件到浏览器
     *
     * @param response  HttpServletResponse对象
     * @param objectKey 文件路径
     * @param fileName  浏览器显示的文件名
     */
    public void safeDownloadFileToResponse(HttpServletResponse response, String objectKey, String fileName) {
        this.safeDownloadFileToResponse(null, response, objectKey, fileName);
    }

    /**
     * 下载文件，直接下载文件到浏览器
     *
     * @param bucketName 存储桶名称
     * @param response   HttpServletResponse对象
     * @param objectKey  文件路径
     * @param fileName   浏览器显示的文件名
     */
    public void safeDownloadFileToResponse(String bucketName, HttpServletResponse response, String objectKey, String fileName) {
        OSS client = this.getClient();
        boolean objectExists = this.objectExists(client, bucketName, objectKey);

        if (!objectExists) {
            log.error("文件已删除或过期，下载失败: bucket={}, objectKey={}", bucketName, objectKey);
            throw new IllegalArgumentException("文件已删除或过期，下载失败");
        }

        String targetBucketName = this.getBucketName(bucketName);

        try {
            // 获取对象
            OSSObject ossObject = client.getObject(targetBucketName, objectKey);
            if (ossObject == null || ossObject.getObjectContent() == null) {
                log.warn("下载文件失败，对象不存在或内容为空: bucket={}, objectKey={}", targetBucketName, objectKey);
                throw new RuntimeException("下载文件失败，对象不存在或内容为空");
            }

            response.reset();
            response.setContentType("application/octet-stream");
            response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            response.setHeader("Content-Length", String.valueOf(ossObject.getObjectMetadata().getContentLength()));

            // 读取内容
            try (InputStream inputStream = ossObject.getObjectContent();
                 BufferedInputStream bis = new BufferedInputStream(inputStream);
                 BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream())) {
                byte[] buffer = new byte[2048];
                int len;
                while ((len = bis.read(buffer)) != -1) {
                    bos.write(buffer, 0, len);
                }
            }
        } catch (Exception e) {
            log.error("下载文件失败: bucket={}, objectKey={}", bucketName, objectKey, e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 判断文件是否存在
     *
     * @param objectKey OSS对象 Key（即文件路径）
     * @return true 表示存在；false 表示不存在
     */
    public boolean objectExists(String objectKey) {
        return this.objectExists(null, objectKey);
    }

    /**
     * 判断 OSS 文件是否存在（安全返回 true/false，不抛异常）
     */
    public boolean objectExists(String bucketName, String objectKey) {
        return this.objectExists(this.getClient(), bucketName, objectKey);
    }

    /**
     * 复制 OSS 路径下的单个文件
     *
     * @param sourceKey      源文件路径
     * @param destinationKey 目标文件路径
     */
    public void copyOssObject(String sourceKey, String destinationKey) {
        this.copyOssObject(null, sourceKey, null, destinationKey);
    }

    /**
     * 复制 OSS 路径下的单个文件
     *
     * @param sourceBucketName      源存储桶名称
     * @param sourceKey             源文件路径
     * @param destinationBucketName 目标存储桶名称
     * @param destinationKey        目标文件路径
     */
    public void copyOssObject(String sourceBucketName, String sourceKey, String destinationBucketName, String destinationKey) {
        this.validateCopyParams(sourceBucketName, sourceKey, destinationBucketName, destinationKey);

        OSS client = this.getClient();
        String actualSourceBucket = this.getBucketName(sourceBucketName);
        String actualTargetBucket = this.getBucketName(destinationBucketName);

        // 拷贝文件。
        try {
            client.copyObject(sourceBucketName, sourceKey, destinationBucketName, destinationKey);
            log.info("复制文件成功: sourceBucket={}, sourceKey={}, targetBucket={}, targetKey={}", actualSourceBucket, sourceKey, actualTargetBucket, destinationKey);
        } catch (Exception e) {
            log.error("复制文件失败: sourceBucket={}, sourceKey={}, targetBucket={}, targetKey={}", actualSourceBucket, sourceKey, actualTargetBucket, destinationKey, e);
            throw new RuntimeException("复制文件失败", e);
        }
    }

    /**
     * 复制 OSS 路径目录下的所有文件
     */
    public void copyOssDir(String sourcePrefix, String targetPrefix) {
        this.copyOssDir(null, sourcePrefix, null, targetPrefix, false);
    }

    /**
     * 复制 OSS 路径目录下的所有文件
     */
    public void copyOssDir(String sourceBucket, String sourcePrefix, String targetBucket,
                           String targetPrefix) {
        this.copyOssDir(sourceBucket, sourcePrefix, targetBucket, targetPrefix, false);
    }

    /**
     * 并发复制 OSS 路径目录下的所有文件
     */
    public void copyOssDirAsync(String sourcePrefix, String targetPrefix) {
        this.copyOssDir(null, sourcePrefix, null, targetPrefix, true);
    }

    /**
     * 并发复制 OSS 路径目录下的所有文件
     */
    public void copyOssDirAsync(String sourceBucket, String sourcePrefix, String targetBucket,
                                String targetPrefix) {
        this.copyOssDir(sourceBucket, sourcePrefix, targetBucket, targetPrefix, true);
    }

    /**
     * 读取 OSS 中文件内容为字符串（用于读取公钥/私钥/配置文件等）
     *
     * @param objectKey OSS 文件路径
     * @return 文件内容字符串
     */
    public String readObjectAsString(String objectKey) {
        return this.readObjectAsString(null, objectKey);
    }

    /**
     * 读取 OSS 中文件内容为字符串（可指定 bucket）
     *
     * @param bucketName OSS bucket 名称
     * @param objectKey  OSS 文件路径
     * @return 文件内容字符串
     */
    public String readObjectAsString(String bucketName, String objectKey) {
        boolean validObjectKey = this.isValidObjectKey(objectKey);
        if (!validObjectKey) {
            log.warn("读取文件内容失败, 路径不合法, objectKey={}", objectKey);
            throw new IllegalArgumentException("读取文件内容失败, 路径不合法");
        }
        OSS client = getClient();
        String targetBucket = this.getBucketName(bucketName);
        try {
            OSSObject ossObject = client.getObject(targetBucket, objectKey);
            try (InputStream inputStream = ossObject.getObjectContent()) {
                // 使用 Hutool 工具简化处理
                return IoUtil.read(inputStream, CharsetUtil.CHARSET_UTF_8)
                        .replace("\r\n", "")
                        .replace("\n", "");
            }
        } catch (Exception e) {
            log.error("读取OSS对象内容失败: bucket={}, objectKey={}", bucketName, objectKey, e);
            throw new RuntimeException("读取OSS对象内容失败", e);
        }
    }

    /**
     * 向 OSS 文件直接写入内容（覆盖模式），不判断文件是否存在，强制覆盖
     */
    public void writeOverride(String content, String objectKey) {
        this.writeOverride(null, content, objectKey);
    }

    /**
     * 向 OSS 文件直接写入内容（覆盖模式），不判断文件是否存在，强制覆盖
     */
    public void writeOverride(String bucketName, String content, String objectKey) {
        boolean validObjectKey = this.isValidObjectKey(objectKey);
        if (!validObjectKey) {
            log.warn("写入文件内容失败, 路径不合法, objectKey={}", objectKey);
            throw new IllegalArgumentException("写入文件内容失败, 路径不合法");
        }
        OSS client = getClient();
        String targetBucket = this.getBucketName(bucketName);

        try {
            byte[] bytes = content.getBytes(CharsetUtil.CHARSET_UTF_8);
            client.putObject(targetBucket, objectKey, new ByteArrayInputStream(bytes));
            log.info("OSS 文件写入成功（覆盖模式）: bucket={}, objectKey={}", targetBucket, objectKey);
        } catch (Exception e) {
            log.error("OSS 文件写入失败（覆盖模式）: bucket={}, objectKey={}", targetBucket, objectKey, e);
            throw new RuntimeException("OSS 覆盖写入失败", e);
        }
    }

    /**
     * 向 OSS 文件追加内容，若文件不存在则抛出异常
     *
     * @param content   要追加的内容
     * @param objectKey 文件路径
     */
    public void appendOrThrow(String content, String objectKey) {
        this.appendOrThrow(null, content, objectKey);
    }

    /**
     * 向 OSS 文件追加内容（带 Bucket），若文件不存在则抛出异常
     *
     * @param bucketName Bucket 名称
     * @param content    要追加的内容
     * @param objectKey  文件路径
     */
    public void appendOrThrow(String bucketName, String content, String objectKey) {
        boolean validObjectKey = this.isValidObjectKey(objectKey);
        if (!validObjectKey) {
            log.warn("追加文件内容失败, 路径不合法, objectKey={}", objectKey);
            throw new IllegalArgumentException("追加文件内容失败, 路径不合法");
        }
        OSS client = getClient();
        String targetBucket = this.getBucketName(bucketName);

        if (!this.objectExists(client, bucketName, objectKey)) {
            throw new RuntimeException("OSS 文件不存在，无法追加写入: " + objectKey);
        }

        try (InputStream input = client.getObject(targetBucket, objectKey)
                .getObjectContent()) {
            String existing = IoUtil.read(input, CharsetUtil.CHARSET_UTF_8);
            String merged = existing + content;
            client.putObject(targetBucket, objectKey,
                    new ByteArrayInputStream(merged.getBytes(CharsetUtil.CHARSET_UTF_8)));
            log.info("OSS 文件追加成功（存在校验）: bucket={}, objectKey={}", targetBucket, objectKey);
        } catch (Exception e) {
            log.error("OSS 文件追加失败（存在校验）: bucket={}, objectKey={}", targetBucket, objectKey, e);
            throw new RuntimeException("OSS 追加写入失败", e);
        }
    }

    /**
     * 向 OSS 文件追加内容，若文件不存在则自动新建
     *
     * @param content   要追加的内容
     * @param objectKey 文件路径
     */
    public void appendOrCreate(String content, String objectKey) {
        appendOrCreate(null, content, objectKey);
    }

    /**
     * 向 OSS 文件追加内容（带 Bucket），若文件不存在则自动新建
     *
     * @param bucketName Bucket 名称
     * @param content    要追加的内容
     * @param objectKey  文件路径
     */
    public void appendOrCreate(String bucketName, String content, String objectKey) {
        OSS client = getClient();
        String targetBucket = this.getBucketName(bucketName);

        try {
            String merged = content;
            if (this.objectExists(client, bucketName, objectKey)) {
                try (InputStream input = client.getObject(targetBucket, objectKey)
                        .getObjectContent()) {
                    String existing = IoUtil.read(input, CharsetUtil.CHARSET_UTF_8);
                    merged = existing + content;
                }
            }
            client.putObject(targetBucket, objectKey,
                    new ByteArrayInputStream(merged.getBytes(CharsetUtil.CHARSET_UTF_8)));
            log.info("OSS 文件追加成功（自动新建）: bucket={}, objectKey={}", targetBucket, objectKey);
        } catch (Exception e) {
            log.error("OSS 文件追加失败（自动新建）: bucket={}, objectKey={}", targetBucket, objectKey, e);
            throw new RuntimeException("OSS 追加写入失败", e);
        }
    }

    /**
     * 复制 OSS 路径目录下的所有文件（支持同/跨 Bucket，支持并发复制）
     *
     * @param sourceBucket 源 Bucket 名称（可为 null 表示默认）
     * @param sourcePrefix 源目录前缀（如 "images/a/"）
     * @param targetBucket 目标 Bucket 名称（可为 null 表示默认）
     * @param targetPrefix 目标目录前缀（如 "images/b/"）
     * @param parallel     是否启用并发复制（true 并发，false 顺序执行）
     */
    private void copyOssDir(String sourceBucket, String sourcePrefix, String targetBucket,
                            String targetPrefix, boolean parallel) {

        this.validateCopyParams(sourceBucket, sourcePrefix, targetBucket, targetPrefix);

        OSS client = this.getClient();
        String actualSourceBucket = this.getBucketName(sourceBucket);
        String actualTargetBucket = this.getBucketName(targetBucket);

        List<Future<?>> futures = new ArrayList<>();
        ListObjectsRequest request =
                new ListObjectsRequest(actualSourceBucket).withPrefix(sourcePrefix)
                        .withMaxKeys(1000);

        ObjectListing listing;
        do {
            listing = client.listObjects(request);
            for (OSSObjectSummary obj : listing.getObjectSummaries()) {
                this.submitCopyTask(client, actualSourceBucket, obj.getKey(), actualTargetBucket,
                        sourcePrefix, targetPrefix, parallel, futures);
            }
            request.setMarker(listing.getNextMarker());
        } while (listing.isTruncated());

        if (parallel) {
            this.waitForFutures(futures);
        }

        log.info("OSS目录复制完成: {}:{} -> {}:{}, 并发: {}", sourceBucket, sourcePrefix, targetBucket,
                targetPrefix, parallel);
    }

    /**
     * 校验复制参数合法性（Bucket 和路径不能为空，不能复制自身）
     */
    private void validateCopyParams(String sourceBucket, String sourcePrefix, String targetBucket, String targetPrefix) {
        if (!StringUtils.hasText(sourcePrefix) || !StringUtils.hasText(targetPrefix)) {
            throw new IllegalArgumentException("复制路径不能为空");
        }
        String src = this.getBucketName(sourceBucket);
        String dst = this.getBucketName(targetBucket);
        if (src.equals(dst) && sourcePrefix.equals(targetPrefix)) {
            throw new IllegalArgumentException("源路径与目标路径不能相同");
        }
    }

    /**
     * 提交复制任务（串行或并发执行）
     *
     * @param parallel 是否并发执行
     * @param futures  用于记录异步 Future（并发时使用）
     */
    private void submitCopyTask(OSS client, String sourceBucket, String sourceKey,
                                String targetBucket, String sourcePrefix, String targetPrefix,
                                boolean parallel, List<Future<?>> futures) {

        Runnable task = () -> this.copyObjectSafe(client, sourceBucket, sourceKey, targetBucket,
                sourcePrefix, targetPrefix);

        if (parallel) {
            futures.add(ossCopyExecutor.submit(task));
        } else {
            task.run();
        }
    }

    /**
     * 等待所有并发复制任务完成（忽略异常）
     */
    private void waitForFutures(List<Future<?>> futures) {
        for (Future<?> future : futures) {
            try {
                future.get();
            } catch (Exception e) {
                log.error("并发复制任务异常", e);
            }
        }
    }

    /**
     * 执行单个 OSS 对象复制（带异常兜底）
     */
    private void copyObjectSafe(OSS client, String sourceBucket, String sourceKey,
                                String targetBucket, String sourcePrefix, String targetPrefix) {
        try {
            String targetKey = sourceKey.replaceFirst("^" + Pattern.quote(sourcePrefix), targetPrefix);
            client.copyObject(sourceBucket, sourceKey, targetBucket, targetKey);
            log.info("复制成功: {}:{} -> {}:{}", sourceBucket, sourceKey, targetBucket, targetKey);
        } catch (Exception e) {
            log.error("复制失败: {}:{} -> {}:{}", sourceBucket, sourceKey, targetBucket, targetPrefix, e);
        }
    }

    /**
     * 获取OSS客户端
     */
    private OSS getClient() {
        // 如果缓存中存在配置,直接返回当前客户端
        if (configService.exitInCache()) {
            OSS client = clientManager.getCurrentClient();
            if (client != null) {
                return client;
            }
        }
        // 获取最新配置并创建新客户端
        AliOSSConfig config = configService.getStorageConfig();
        return clientManager.getClient(config);
    }

    /**
     * 获取使用的存储桶名称
     */
    private String getBucketName(String bucketName) {
        if (StringUtils.hasText(bucketName)) {
            return bucketName;
        }
        return clientManager.getBucketName();
    }

    /**
     * 校验 OSS ObjectKey 是否合法（符合阿里云命名规范）
     *
     * @param objectKey 文件名或路径
     * @return true 合法，false 非法
     */
    private boolean isValidObjectKey(String objectKey) {
        if (!StringUtils.hasText(objectKey)) {
            return false;
        }

        // 1. 禁止以 / 或 \ 开头
        if (objectKey.startsWith("/") || objectKey.startsWith("\\")) {
            return false;
        }

        // 2. 校验 UTF-8 编码后的长度
        int utf8Length = objectKey.getBytes(CharsetUtil.CHARSET_UTF_8).length;
        if (utf8Length < 1 || utf8Length > 1023) {
            return false;
        }

        // 3. 检查非法字符（回车、换行、XML非法字符）
        for (char c : objectKey.toCharArray()) {
            if (c == '\r' || c == '\n') {
                return false;
            }
            if (c < 0x20 && c != '\t') {
                return false;
            }
        }

        return true;
    }

    /**
     * 校验ObjectKey列表是否合法（遇到非法立即抛异常）
     *
     * @param objectKeys 待校验的文件Key列表
     */
    private void validateObjectKeysOrThrow(List<String> objectKeys) {
        for (String key : objectKeys) {
            if (!isValidObjectKey(key)) {
                log.error("检测到非法ObjectKey: {}", key);
                throw new IllegalArgumentException("文件操作失败，存在非法ObjectKey: " + key);
            }
        }
    }

    /**
     * 过滤合法的ObjectKey列表（自动剔除非法路径并记录日志）
     *
     * @param objectKeys 待校验的文件Key列表
     * @return 合法的文件Key列表
     */
    private List<String> filterValidObjectKeys(List<String> objectKeys) {
        List<String> validKeys = new ArrayList<>();
        List<String> invalidKeys = new ArrayList<>();

        for (String key : objectKeys) {
            if (isValidObjectKey(key)) {
                validKeys.add(key);
            } else {
                invalidKeys.add(key);
            }
        }

        if (!invalidKeys.isEmpty()) {
            log.warn("检测到非法ObjectKey，已跳过删除: {}", invalidKeys);
        }

        return validKeys;
    }

    /**
     * 执行OSS批量删除操作（基于合法的文件Key列表）
     *
     * @param bucketName 目标存储桶名称
     * @param validKeys  已校验通过的文件Key列表
     */
    private void deleteKeysFromBucket(String bucketName, List<String> validKeys) {
        OSS client = getClient();
        String targetBucket = this.getBucketName(bucketName);

        try {
            DeleteObjectsRequest request = new DeleteObjectsRequest(targetBucket).withKeys(validKeys).withQuiet(true);

            client.deleteObjects(request);
            log.info("批量安全删除成功: bucket={}, keys={}", targetBucket, validKeys);
        } catch (Exception e) {
            log.error("批量安全删除失败: bucket={}, keys={}", targetBucket, validKeys, e);
            throw new RuntimeException("批量安全删除 OSS 文件失败", e);
        }
    }

    /**
     * 下载文件
     */
    private byte[] downloadFile(OSS client, String bucketName, String objectKey) {
        // **注意** 如果要放开的话, 需要加上对 objectKey 的参数校验
        // 这里没加是因为 safeDownloadFile 在调用 objectExists() 方法的时候已经包含
        String targetBucket = this.getBucketName(bucketName);
        try {
            // 获取对象
            OSSObject ossObject = client.getObject(targetBucket, objectKey);
            if (ossObject == null || ossObject.getObjectContent() == null) {
                log.warn("下载文件失败，对象不存在或内容为空: bucket={}, objectKey={}", targetBucket, objectKey);
                return null;
            }

            // 读取内容
            try (InputStream inputStream = ossObject.getObjectContent();
                 ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
                // 获取最终的字节数组
                return outputStream.toByteArray();
            }
        } catch (Exception e) {
            log.error("下载文件失败: bucket={}, objectKey={}", bucketName, objectKey, e);
            throw new RuntimeException("下载文件失败", e);
        }
    }

    /**
     * 判断 OSS 文件是否存在（安全返回 true/false，不抛异常）
     */
    private boolean objectExists(OSS client, String bucketName, String objectKey) {
        boolean validObjectKey = this.isValidObjectKey(objectKey);
        if (!validObjectKey) {
            log.warn("判断文件是否存在失败, 路径不合法, objectKey={}", objectKey);
            throw new IllegalArgumentException("判断文件是否存在失败, 路径不合法");
        }

        String targetBucket = getBucketName(bucketName);
        try {
            return client.doesObjectExist(targetBucket, objectKey);
        } catch (OSSException e) {
            // 注意，当你使用JDK8以上的版本且没有添加以下JAXB相关依赖，就会出现下面的异常。当你使用JDK8版本可以忽略此异常。
            String rawXml = e.getRawResponseError();
            if (rawXml != null && rawXml.contains("<Code>NoSuchKey</Code>")) {
                return false;
            }
            log.warn("OSS返回异常但不为NoSuchKey: bucket={}, key={}, err={}", targetBucket, objectKey, e.getErrorCode(), e);
            return false;
        } catch (Exception e) {
            log.error("OSS对象存在性检查失败: bucket={}, key={}", targetBucket, objectKey, e);
            return false;
        }
    }

}
